package com.atguigu.tingshu.common.aspect;

import com.alibaba.fastjson.JSON;
import com.atguigu.tingshu.common.anno.GuiGuCache;
import com.atguigu.tingshu.common.constant.RedisConstant;
import com.atguigu.tingshu.model.album.AlbumInfo;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.expression.Expression;
import org.springframework.expression.ParserContext;
import org.springframework.expression.spel.standard.SpelExpressionParser;
import org.springframework.expression.spel.support.StandardEvaluationContext;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;

import java.lang.reflect.Method;
import java.lang.reflect.Type;
import java.util.concurrent.TimeUnit;

@Component
@Aspect
@Slf4j
public class GuiGuCacheAspect {

    @Autowired
    private StringRedisTemplate stringRedisTemplate;

    @Autowired
    private RedissonClient redissonClient;

    @Around(value = "@annotation(cache)")
    public Object around(ProceedingJoinPoint proceedingJoinPoint, GuiGuCache cache){
        // 先从Redis中查询数据
        String cacheKeyExpression = cache.cacheKey();
        String redisKey = parseExpression(proceedingJoinPoint, cacheKeyExpression, String.class);
        //从redis中获取数据
        String albumInfoJson = stringRedisTemplate.opsForValue().get(redisKey);

        //获取目标方法的返回值
        MethodSignature methodSignature = (MethodSignature) proceedingJoinPoint.getSignature();
        Method method = methodSignature.getMethod();
        Type genericReturnType = method.getGenericReturnType();

        if(!StringUtils.isEmpty(albumInfoJson)){
            log.info("从redis中获取到数据");
            return JSON.parseObject(albumInfoJson, genericReturnType);
        }
        //判断是否需要使用分布式锁
        boolean enableLock = cache.enableLock();
        String lockName = cache.lockName();
        if(enableLock && !StringUtils.isEmpty(lockName)){
            //对lockName进行解析
            lockName = parseExpression(proceedingJoinPoint, lockName, String.class);
            //获取分布式锁
            RLock lock = redissonClient.getLock(lockName);
            boolean tryLock = lock.tryLock();
            if(tryLock){
                try {
                    log.info(Thread.currentThread().getId() + "获取分布式锁成功");
                    //从数据库中获取数据
                    Object proceed = proceedingJoinPoint.proceed();
                    //查询到的数据存储在redis中
                    stringRedisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed),7, TimeUnit.DAYS);
                    return proceed;
                }catch (Throwable e){
                    e.printStackTrace();
                    return null;
                } finally {
                    lock.unlock();
                }
            }else{
                log.info(Thread.currentThread().getId() + "获取分布式锁失败");
                return around(proceedingJoinPoint,cache);
            }
        }else{
            //从数据库中获取数据
            Object proceed = null;
            try {
                // 调用getAlbumInfoFromDB从数据库中查询专辑数据
                proceed = proceedingJoinPoint.proceed();
                //查询到的数据存储在redis中
                stringRedisTemplate.opsForValue().set(redisKey,JSON.toJSONString(proceed),7, TimeUnit.DAYS);
                return proceed;
            } catch (Throwable e) {
                throw new RuntimeException(e);
            }

        }

    }

    private SpelExpressionParser spelExpressionParser = new SpelExpressionParser();
    //对表达式进行解析
    public <T> T parseExpression(ProceedingJoinPoint proceedingJoinPoint, String expressionStr, Class<T> clazz){
        Expression expression = spelExpressionParser.parseExpression(expressionStr, ParserContext.TEMPLATE_EXPRESSION);
        StandardEvaluationContext standardEvaluationContext = new StandardEvaluationContext();
        standardEvaluationContext.setVariable("params",proceedingJoinPoint.getArgs());
        T value = expression.getValue(standardEvaluationContext, clazz);
        return value;
    }

}

